﻿using System;
using System.Linq;
using System.Runtime.CompilerServices;

namespace RayDen.Library.Core.Primitives {
    using System.ComponentModel;
    using System.Runtime.InteropServices;

    [TypeConverter(typeof(ExpandableObjectConverter))]
    [Serializable]
    [StructLayout(LayoutKind.Sequential)]
    public struct Normal : IEquatable<Normal>,ISerializableValue {
        public static float Epsilon = 0.00000001f;
        public static Normal Zero = new Normal(0f);
        public float x, y, z;


        public float X {
            get { return x; }
            set { x = value; }
        }

        public float Y {
            get { return y; }
            set { y = value; }
        }

        public float Z {
            get { return z; }
            set { z = value; }
        }
        public static unsafe readonly int SizeInBytes = sizeof(Normal);
        public float this[int index] {
            get {
                switch( index ) {
                    case 0:
                        return x;
                    case 1:
                        return y;
                    case 2:
                        return z;
                    default:
                        throw new ArgumentException("index");
                }
            }

            set {
                switch( index ) {
                    case 0:
                        x = value;
                        break;
                    case 1:
                        y = value;
                        break;
                    case 2:
                        z = value;
                        break;
                    default:
                        throw new ArgumentException("index");
                }
            }

        }

        public float Component( Axis p ) {
            switch( p ) {
                case Axis.PLANE_X:
                    return x;
                case Axis.PLANE_Y:
                    return y;
                case Axis.PLANE_Z:
                    return z;
                default:
                    throw new ArgumentException("index");
            }
        }


        public Normal( float a ) {
#if DEBUG
            RayDen.Library.Core.Components.Assert.IsNotNaN(a);
#endif
            x = y = z = a;
            //     w = 0;
        }


        public Normal( float a, float b, float c ) {
            x = a;
            y = b;
            z = c;
#if DEBUG
            RayDen.Library.Core.Components.Assert.IsNotNaN(x);
            RayDen.Library.Core.Components.Assert.IsNotNaN(y);
            RayDen.Library.Core.Components.Assert.IsNotNaN(z);
#endif
            // w = 0;
        }

        public Normal( ref Normal v ) {
            x = v.x;
            y = v.y;
            z = v.z;
#if DEBUG
            RayDen.Library.Core.Components.Assert.IsNotNaN(x);
            RayDen.Library.Core.Components.Assert.IsNotNaN(y);
            RayDen.Library.Core.Components.Assert.IsNotNaN(z);
#endif
            //   w = 0;
        }
        public Normal( float[] val ) {
            x = val[0];
            y = val[1];
            z = val[2];
#if DEBUG
            RayDen.Library.Core.Components.Assert.IsNotNaN(x);
            RayDen.Library.Core.Components.Assert.IsNotNaN(y);
            RayDen.Library.Core.Components.Assert.IsNotNaN(z);
#endif
            // w = 0;
        }
        public Normal( Normal v ) {
            x = v.x;
            y = v.y;
            z = v.z;
#if DEBUG
            RayDen.Library.Core.Components.Assert.IsNotNaN(x);
            RayDen.Library.Core.Components.Assert.IsNotNaN(y);
            RayDen.Library.Core.Components.Assert.IsNotNaN(z);
#endif
            // w = 0;
        }
        public Normal(Vector v) {
            x = v.x;
            y = v.y;
            z = v.z;
#if DEBUG
            RayDen.Library.Core.Components.Assert.IsNotNaN(x);
            RayDen.Library.Core.Components.Assert.IsNotNaN(y);
            RayDen.Library.Core.Components.Assert.IsNotNaN(z);
#endif
            // w = 0;
        }


        public bool Equals( Normal other ) {
            float dx = Math.Abs(other.x - x);
            float dy = Math.Abs(other.y - y);
            float dz = Math.Abs(other.z - z);

            return (dx < Epsilon) && (dy < Epsilon) && (dz < Epsilon);
        }

        public static explicit operator Normal( Vector n ) {
            return new Normal(n.x, n.y, n.z);
        }

        public static Normal operator +( Normal v1, Normal v2 ) {
            return Normal.Add(ref v1, ref v2);
        }

        public static Normal operator -( Normal v1, Normal v2 ) {
            return Normal.Sub(ref v1, ref v2);
        }

        public static Normal operator *( Normal v, float f ) {
            return new Normal(v.x * f, v.y * f, v.z * f);
        }

        public static Normal operator *( float f, Normal v ) {
            return new Normal(v.x * f, v.y * f, v.z * f);
        }

        public static Normal operator /( Normal v, float f ) {
            float divider = 1.0f / f;
            return new Normal(v.x * divider, v.y * divider, v.z * divider);
        }

        public static Normal operator /( float f, Normal v ) {
            return new Normal(f / v.x, f / v.y, f / v.z);
        }
        public static Normal operator -( Normal v ) {
            return new Normal(-v.x, -v.y, -v.z);
        }
        public static Normal Add( ref Normal a, ref Normal b ) {
            return new Normal(a.x + b.x, a.y + b.y, a.z + b.z);
        }

        public static Normal Sub( ref Normal a, ref Normal b ) {
            return new Normal(a.x - b.x, a.y - b.y, a.z - b.z);
        }

        public float Length {
            get {
                return MathLab.Sqrt(x * x + y * y + z * z);
            }
        }

        public float Length2 {
            get { return x * x + y * y + z * z; }
        }

        public Normal Normalize() {
            float l = 1f / MathLab.Sqrt(x * x + y * y + z * z);
            //InvSqrt(x * x + y * y + z * z);
            x *= l;
            y *= l;
            z *= l;

            return this;
        }

        public float Dot( ref Vector b )
        {
            return (x * b.x + y * b.y + z * b.z);
        }
        public float Dot( ref Normal b )
        {
            return (x * b.x + y * b.y + z * b.z);
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static float Dot( ref Normal a, ref Normal b ) {
            return (a.x * b.x + a.y * b.y + a.z * b.z);
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static float AbsDot( ref Normal a, ref Normal b ) {
            return Math.Abs(a.x * b.x + a.y * b.y + a.z * b.z);
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static Vector Cross( ref Vector v1, ref Normal v2 ) {
            return new Vector(
                  (v1.y * v2.z) - (v1.z * v2.y),
                  (v1.z * v2.x) - (v1.x * v2.z),
                  (v1.x * v2.y) - (v1.y * v2.x));
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static Vector Cross( ref Normal v1, ref  Vector v2 ) {
            return new Vector(
                  (v1.y * v2.z) - (v1.z * v2.y),
                  (v1.z * v2.x) - (v1.x * v2.z),
                  (v1.x * v2.y) - (v1.y * v2.x));
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static float Dot( ref Normal n1, ref Vector v2 ) {
            return n1.x * v2.x + n1.y * v2.y + n1.z * v2.z;
        }
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static float Dot( ref Vector v2, ref Normal n1 ) {
            return n1.x * v2.x + n1.y * v2.y + n1.z * v2.z;
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static float AbsDot( ref Normal a, ref Vector b ) {
            return Math.Abs(a.x * b.x + a.y * b.y + a.z * b.z);
        }


        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static float AbsDot( ref Vector a, ref  Normal b ) {
            return Math.Abs(a.x * b.x + a.y * b.y + a.z * b.z);
        }

        public bool IsZero()
        {
            return (Math.Abs(x+y+z) <= 0f);
            //return (Math.Abs(x) <= Epsilon) & (Math.Abs(y) <= Epsilon) & (Math.Abs(z) <= Epsilon);
        }
        public float[] ToArray() {
            return new [] { x, y, z };
        }
        public override string ToString()
        {
            return string.Format("{0:F4}, {1:F4}, {2:F4}", x, y, z);
        }
        //Hush on you
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public Vector ToVec() {
            return new Vector(x, y, z);
        }

        public byte[] Serialize()
        {
            return new[]
                {
                    BitConverter.GetBytes(x),
                    BitConverter.GetBytes(y),
                    BitConverter.GetBytes(z)
                }.SelectMany(t => t).ToArray();
        }

        public void Deserialize(byte[] data, int offset = 0)
        {
            x = BitConverter.ToSingle(data, 0 + offset);
            y = BitConverter.ToSingle(data, 4 + offset);
            z = BitConverter.ToSingle(data, 8 + offset);
        }
    }
}